﻿namespace AY38910.Player
{
    using System;
    using System.Linq;
    using System.Threading;

    internal abstract class Player
    {
        #region Fields

        private readonly IOutputDevice outputDevice;

        private int currentFrame;

        private int delay;

        private Timer timer;

        #endregion

        #region Constructors and Destructors

        protected Player(IOutputDevice outputDevice)
        {
            this.outputDevice = outputDevice;
        }

        #endregion

        #region Delegates

        public delegate void FramePlayed(object sender, FramePlayedEventArgs eventArgs);

        public delegate void SongPlayed(object sender, EventArgs eventArgs);

        #endregion

        #region Public Events

        public event FramePlayed OnFramePlayed;

        public event SongPlayed OnSongPlayed;

        #endregion

        #region Methods

        protected void Pause()
        {
            this.timer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        protected void Start(YmFile ymFile)
        {
            if (ymFile == null)
            {
                return;
            }

            this.delay = (int)(((float)1 / ymFile.FrameSpeed) * 1000);

            this.currentFrame = 0;

            if (this.timer != null)
            {
                // If there's still an active timer, stop it and dispose it.
                this.timer.Change(Timeout.Infinite, Timeout.Infinite);
                this.timer.Dispose();
                this.timer = null;
            }
            var previousData = new byte[14];

            this.timer = new Timer(
                state =>
                {
                    if (this.currentFrame >= ymFile.FrameCount)
                    {
                        if (this.timer != null)
                        {
                            this.timer.Change(Timeout.Infinite, Timeout.Infinite);
                            this.timer.Dispose();
                            this.timer = null;
                        }

                        if (this.OnSongPlayed != null)
                        {
                            this.OnSongPlayed(this, EventArgs.Empty);
                        }

                        ////  this.ResetPsg();

                        return;
                    }

                    var frame = ymFile.Frames[this.currentFrame];

                    var data = new[]
                               {
                                   frame.VoiceAPeriodCourse,
                                   frame.VoiceAPeriodFine,
                                   frame.VoiceBPeriodCourse,
                                   frame.VoiceBPeriodFine,
                                   frame.VoiceBPeriodCourse,
                                   frame.VoiceCPeriodFine,
                                   frame.NoisePeriod,
                                   frame.MixerControl,
                                   frame.VolumeControlA,
                                   frame.VolumeControlB,
                                   frame.VolumeControlC,
                                   frame.EnvelopeCourse,
                                   frame.EnvelopeFine,
                                   frame.EnvelopeShape
                               };

                    if (previousData.Zip(data, (p, d) => p != d).Any(x => x))
                    {
                        this.outputDevice.Write(data);
                    }

                    previousData = data;

                    if (this.OnFramePlayed != null)
                    {
                        this.OnFramePlayed(this, new FramePlayedEventArgs(this.currentFrame, (int)ymFile.FrameCount));
                    }

                    //// this.SetProgress(currentFrame, (int)ymFile.FrameCount);

                    Interlocked.Increment(ref this.currentFrame);
                },
                null,
                0,
                this.delay);
        }

        #endregion
    }
}